package ui.swing.list;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.StringTokenizer;

import javax.swing.AbstractListModel;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

public class StatesList2 extends JFrame {

	private static final long serialVersionUID = 2802181989239204378L;

	protected JList m_statesList;

	// Radio buttons to change layout policy
	protected JRadioButton m_verticalRb;
	protected JRadioButton m_verticalWrapRb;
	protected JRadioButton m_horizontalWrapRb;

	// Radio buttons used to switch between models
	protected JRadioButton m_longRb;
	protected JRadioButton m_shortRb;

	public static ArrayModel LONG_MODEL = new ArrayModel(new String[] {
			"AK\tAlaska\tJuneau",
			"AL\tAlabama\tMontgomery",
			"AR\tArkansas\tLittle Rock",
			"AZ\tArizona\tPhoenix",
			"CA\tCalifornia\tSacramento",
			"CO\tColorado\tDenver",
			"CT\tConnecticut\tHartford",
			"DE\tDelaware\tDover",
			"FL\tFlorida\tTallahassee",
			"GA\tGeorgia\tAtlanta",
			"HI\tHawaii\tHonolulu",
			"IA\tIowa\tDes Moines",
			"ID\tIdaho\tBoise",
			"IL\tIllinois\tSpringfield",
			"IN\tIndiana\tIndianapolis",
			"KS\tKansas\tTopeka",
			"KY\tKentucky\tFrankfort",
			"LA\tLouisiana\tBaton Rouge",
			"MA\tMassachusetts\tBoston",
			"MD\tMaryland\tAnnapolis",
			"ME\tMaine\tAugusta",
			"MI\tMichigan\tLansing",
			"MN\tMinnesota\tSt. Paul",
			"MO\tMissouri\tJefferson City",
			"MS\tMississippi\tJackson",
			"MT\tMontana\tHelena",
			"NC\tNorth Carolina\tRaleigh",
			"ND\tNorth Dakota\tBismark",
			"NE\tNebraska\tLincoln",
			"NH\tNew Hampshire\tConcord",
			"NJ\tNew Jersey\tTrenton",
			"NM\tNew Mexico\tSanta Fe",
			"NV\tNevada\tCarson City",
			"NY\tNew York\tAlbany",
			"OH\tOhio\tColumbus",
			"OK\tOklahoma\tOklahoma City",
			"OR\tOregon\tSalem",
			"PA\tPennsylvania\tHarrisburg",
			"RI\tRhode Island\tProvidence",
			"SC\tSouth Carolina\tColumbia",
			"SD\tSouth Dakota\tPierre",
			"TN\tTennessee\tNashville",
			"TX\tTexas\tAustin",
			"UT\tUtah\tSalt Lake City",
			"VA\tVirginia\tRichmond",
			"VT\tVermont\tMontpelier",
			"WA\tWashington\tOlympia",
			"WV\tWest Virginia\tCharleston",
			"WI\tWisconsin\tMadison",
			"WY\tWyoming\tCheyenne", });

	public static ArrayModel SHORT_MODEL = new ArrayModel(new String[] { "AK",
			"AL", "AR", "AZ", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "IA",
			"ID", "IL", "IN", "KS", "KY", "LA", "MA", "MD", "ME", "MI", "MN",
			"MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", "NM", "NV", "NY",
			"OH", "OK", "OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VA",
			"VT", "WA", "WV", "WI", "WY" });
	
	public StatesList2() {
		super("States List");
		setSize(450, 250);
		
		// 为JList设置自定义的Model和CellRenderer
		m_statesList = new JList();
		m_statesList.setModel(LONG_MODEL);
		TabListCellRenderer renderer = new TabListCellRenderer();
		renderer.setTabs(new int[] {50, 200, 300});
		m_statesList.setCellRenderer(renderer);
		// Add ListSearcher	KeyListener to JList
		m_statesList.addKeyListener(new KeySearchListener(m_statesList));
		
		JScrollPane jsp = new JScrollPane();
		jsp.setViewportView(m_statesList);
		getContentPane().add(jsp, BorderLayout.CENTER);
		
		// 控制面板
		JPanel pp = new JPanel(new GridLayout(2,3));
		// 控制面板的方向按钮组
		ButtonGroup bg1 = new ButtonGroup();
		m_verticalRb = new JRadioButton("VERTICAL", true);
		pp.add(m_verticalRb);
		bg1.add(m_verticalRb);
		m_verticalWrapRb = new JRadioButton("VERTICAL_WRAP");
		pp.add(m_verticalWrapRb);
		bg1.add(m_verticalWrapRb);
		m_horizontalWrapRb = new JRadioButton("HORIZONTAL_WRAP");
		pp.add(m_horizontalWrapRb);
		bg1.add(m_horizontalWrapRb);
		
		// 控制显示模式的按钮
		ButtonGroup bg2 = new ButtonGroup();
		m_longRb = new JRadioButton("Long Model", true);
		pp.add(m_longRb);
		bg2.add(m_longRb);
		m_shortRb = new JRadioButton("Short Model");
		pp.add(m_shortRb);
		bg2.add(m_shortRb);
		
		getContentPane().add(pp, BorderLayout.NORTH);
		
		// 添加事件监听器
		ActionListener modelListener = new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				if (m_longRb.isSelected()) {
					m_statesList
							.setPrototypeCellValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
					m_statesList.setModel(LONG_MODEL);
				}
				else if (m_shortRb.isSelected()) {
					m_statesList
					.setPrototypeCellValue("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
					m_statesList.setModel(SHORT_MODEL);
				}
			}
		};
		m_longRb.addActionListener(modelListener);
		m_shortRb.addActionListener(modelListener);
		
		ActionListener layoutListener = new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				if (m_verticalRb.isSelected()) {
						m_statesList.setLayoutOrientation(JList.VERTICAL);
				}
				else if (m_verticalWrapRb.isSelected()) {
					m_statesList.setLayoutOrientation(JList.VERTICAL_WRAP);
				}

				else if (m_horizontalWrapRb.isSelected()) {
					m_statesList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
				}
			}
		};
		m_verticalRb.addActionListener(layoutListener);
		m_verticalWrapRb.addActionListener(layoutListener);
		m_horizontalWrapRb.addActionListener(layoutListener);
		
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
	}
	
	public static void main(String[] args) {
		new StatesList2();
	}
}

// Custom cell renderer used to align strings that contain tab characters into
// visual columns
class TabListCellRenderer extends JLabel implements ListCellRenderer {

	private static final long serialVersionUID = -2766850803087471848L;

	protected static Border m_noFocusBorder = new EmptyBorder(1, 1, 1, 1);;

	protected FontMetrics m_fm = null;
	protected Insets m_insets = null; // new Insets(0, 0, 0, 0);
	protected int m_defaultTab = 50;
	protected int[] m_tabs = null;

	public TabListCellRenderer() {
		setOpaque(true);
		setBorder(m_noFocusBorder);		
	}

	public Component getListCellRendererComponent(JList list, Object value,
			int index, boolean isSelected, boolean cellHasFocus) {

		setText(value.toString());

		setBackground(isSelected ? list.getSelectionBackground() : list
				.getBackground());
		setForeground(isSelected ? list.getSelectionForeground() : list
				.getForeground());

		setFont(list.getFont());
		setBorder((cellHasFocus ? UIManager
				.getBorder("List.focusCellHighlightBorder") : m_noFocusBorder));
		return this;
	}

	public void setDefaultTab(int defaultTab) {
		m_defaultTab = defaultTab;
	}

	public void setTabs(int[] tabs) {
		m_tabs = tabs;
	}

	// Method responsible for rendering each cell the getTab() method is used
	// to retrieve the number of pixels corresponding to a given tab index
	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g); // 注意啊，都要先画好"本来的"

		Color colorRetainer = g.getColor();
		m_fm = g.getFontMetrics();

		// 背景和边框
		g.setColor(getBackground());
		g.fillRect(0, 0, getWidth(), getHeight());
		getBorder().paintBorder(this, g, 0, 0, getWidth(), getHeight());

		// 前景
		g.setColor(getForeground());
		g.setFont(getFont());
		m_insets = getInsets();
		int x = m_insets.left;
		int y = m_insets.top + m_fm.getAscent();

		StringTokenizer st = new StringTokenizer(getText(), "\t");
		while (st.hasMoreTokens()) {
			String sNext = st.nextToken();
			g.drawString(sNext, x, y);
			x += m_fm.stringWidth(sNext);

			if (!st.hasMoreTokens())
				break;
			int index = 0;
			while (x >= getTab(index))
				index++;
			x = getTab(index);
		}

		g.setColor(colorRetainer);
	}

	// Method to calculate the distance to use corresponding to a given tab
	// index
	private int getTab(int index) {
		if (m_tabs == null) {
			return m_defaultTab * index;
		}

		int len = m_tabs.length;
		if (index >= 0 && index < len)
			return m_tabs[index];

		return m_tabs[len - 1] + m_defaultTab * (index - len + 1);
	}

}

class ArrayModel extends AbstractListModel {

	private static final long serialVersionUID = -2928084098133248977L;

	Object[] m_data;

	public ArrayModel(Object[] data) {
		m_data = data;
	}

	public Object getElementAt(int index) {
		if (index < 0 || index >= m_data.length)
			return null;
		return m_data[index];
	}

	public int getSize() {
		return m_data.length;
	}

}

class KeySearchListener extends KeyAdapter {

	public static int CHAR_DELTA = 1000;
	
	protected JList m_list;
	protected ListModel m_model;
	protected String m_key = "";
	protected long m_time = 0;
	
	public KeySearchListener(JList list) {
		m_list = list;
		m_model = m_list.getModel();
	}
	
	/*
	 * If key is letter/digit, and event
	 * occurred shortly after last key,
	 * append it to search string and
	 * look for list item with that prefix
	 */
	@Override
	public void keyTyped(KeyEvent e) {
		char ch = e.getKeyChar();
		if (!Character.isLetterOrDigit(ch))
			return;
		
		if(m_time + CHAR_DELTA < System.currentTimeMillis())
			m_key = "";
		m_time = System.currentTimeMillis();
		m_key += Character.toLowerCase(ch);
		for(int k=0; k<m_model.getSize(); k++) {
			String str = ((String)m_model.getElementAt(k)).toLowerCase();
			if(str.startsWith(m_key)) {
				m_list.setSelectedIndex(k);
				m_list.ensureIndexIsVisible(k);
				break;
			}
		}
	}
}
